package mail.fetch.qq;

import mail.entity.ProxyEntity;
import mail.exception.VerifyException;
import mail.util.StatusCode;
import mail.proxy.ProxyManager;
import mail.util.*;
import mail.verify.slider.ThirdPart;
import mail.verify.slider.selenium.TecentSlider;

import javax.swing.*;
import java.io.IOException;
import java.net.*;
import java.net.http.HttpClient;
import java.net.http.HttpHeaders;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.nio.charset.StandardCharsets;
import java.nio.file.Path;
import java.time.Duration;
import java.util.LinkedList;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;

import static com.han.http.util.Print.*;
import static mail.util.StatusCode.QQ_LOGIN_STATUS_OK;

/**
 * @Author: Hanjiafeng
 * @Date: Created in 16:56 2019/1/24
 * @Desc: 使用jdk11, 完成qq邮箱登录
 */
public class Login11 {

    //todo 将所有的抛出异常处理一下,特指SocketException

    /**
     * 定义请求发送对象
     */
    private HttpClient httpClient;

    /**
     * 定义请求Builder
     */
    private HttpRequest.Builder baseRequestBuilder;

    /**
     * 定义cookie管理器
     */
    private CookieManager cookieManager;

    /**
     * 用户名
     */
    private String account;

    /**
     * 密码
     */
    private String password;

    /**
     * 独立密码
     */
    private String independentPwd;

    /**
     * referer uri
     */
    private String refererUri;

    private JsInvoker jsInvoker;

    private Long createIframeStart;

    private ProxyEntity proxyEntity;

    /**
     * 独立密码是必须的,不管有没有设置
     *
     * @param independentPwd 独立密码
     */
    public Login11(String independentPwd, ProxyEntity proxyEntity) {
        this.proxyEntity = proxyEntity;
        this.independentPwd = independentPwd;
        //初始化CookieManager并设置接受所有cookie
        cookieManager = new CookieManager();
        cookieManager.setCookiePolicy(CookiePolicy.ACCEPT_ALL);
        HttpClient.Builder clientBuilder = HttpClient.newBuilder()
                .cookieHandler(cookieManager)
                .followRedirects(HttpClient.Redirect.NEVER);
        if (!Objects.isNull(proxyEntity)) {
            //设置代理
            clientBuilder.proxy(ProxySelector.of(new InetSocketAddress(proxyEntity.getIp(), proxyEntity.getPort())));
        }
        httpClient = clientBuilder
                .build();
        //设置通用参数
        baseRequestBuilder = HttpRequest.newBuilder()
                .timeout(Duration.ofMinutes(1))
                .header("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8")
                .header("Accept-Encoding", "gzip, deflate, br")
                .header("Accept-Language", "zh-CN,zh;q=0.9")
                .header("DNT", "1")
                .header("Upgrade-Insecure-Requests", "1")
                .header("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3664.3 Safari/537.36");

        String f1 = "F:\\workspace\\java\\MailCrawler\\src\\main\\resources\\EncryptQq.js";
        jsInvoker = new JsInvoker(f1);
    }

    /**
     * 扫码登录,获取CookieManager
     *
     * @return CookieManager
     * @throws IOException          io
     * @throws InterruptedException in
     */
    CookieManager getCookieManager() throws IOException, InterruptedException {
        return getBaseCookieManager("", "");
    }

    /**
     * 扫码登录
     *
     * @return checkSIgUrl
     * @throws IOException          IO
     * @throws InterruptedException IN
     */
    private String loginWithoutPassword() throws IOException, InterruptedException {
        QrScanner qrScanner = new QrScanner();
        return qrScanner.watchingQr();
    }

    /**
     * 使用帐号密码登录获取CookieManager
     *
     * @param account  账号
     * @param password 密码
     * @return CookieManager
     * @throws IOException          io
     * @throws InterruptedException in
     */
    public CookieManager getCookieManager(String account, String password) throws IOException, InterruptedException {
        if (Objects.isNull(account) || account.isEmpty() || account.isBlank() || Objects.isNull(password) || password.isEmpty() || password.isBlank()) {
            printlnErr("帐号密码不能为空");
            return null;
        }
        return getBaseCookieManager(account, password);
    }

    /**
     * 根据账号是否为空,决定使用扫码登录或者账号密码登录
     *
     * @param account  帐号
     * @param password 密码
     * @return CookieManager
     * @throws IOException          IO
     * @throws InterruptedException In
     */
    private CookieManager getBaseCookieManager(String account, String password) throws IOException, InterruptedException {
        this.account = account;
        this.password = password;
        cookieManager.getCookieStore().add(URI.create("mail.qq.com"), new HttpCookie("ptui_loginuin", account));

        String xLoginUrl = origin();
        xLogin(xLoginUrl);
        String checkSigUrl;
        //如果账号为null,采用扫码登录,否则采用帐号密码登录
        if (account.isEmpty()) {
            checkSigUrl = loginWithoutPassword();
        } else {
            checkSigUrl = loginWithPassword();
        }
        String readUrl = checkSig(checkSigUrl);
        createIframeStart = System.currentTimeMillis();
        String loginVtUrl = readTemplate(readUrl);
        String redirectUrl = loginVt(loginVtUrl);
        String function = RegexSearcher.matchFirst(redirectUrl, "(?<=/cgi-bin/)((?!\\?).)*(?=\\?)").toLowerCase();
        switch (function) {
            case StatusCode.MAIL_NORMAL:
                break;
            case StatusCode.MAIL_NEED_INDEPENDENT_PASSWORD:
                printlnInfo("输入独立密码");
                //登录失败
                boolean isSu;
                do {
                    isSu = independentPage(redirectUrl);
                    try {
                        Thread.sleep(1000 * 3);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                } while (!isSu);
                break;
            case StatusCode.MAIL_NOT_AVAILABLE:
                printlnInfo("邮箱尚未开通");
                //开通邮箱
                boolean status = openEMail(redirectUrl);
                if (!status) {
                    printlnErr("开通邮箱失败");
                    return null;
                }
                printlnInfo("开通邮箱成功");
                break;
            default:
                printlnErr("loginVt: " + redirectUrl);
                break;
        }
        return cookieManager;
    }

    /**
     * 帐号密码登录
     *
     * @return checkSig
     * @throws IOException          io
     * @throws InterruptedException in
     */
    private String loginWithPassword() throws IOException, InterruptedException {
        PasswordInputer passwordInputer = new PasswordInputer();
        boolean needSlider = passwordInputer.checkRegMaster();
        if (needSlider) {
            passwordInputer.checkoutSlider();
        }
        return passwordInputer.loginU();
    }

    /**
     * 初始化登录界面,并解析出下一步的url
     *
     * @return 返回解析出的url
     * @throws IOException          IO异常
     * @throws InterruptedException 中断异常
     */
    private String origin() throws IOException, InterruptedException {

        HttpRequest request = baseRequestBuilder
                .copy()
                .uri(URI.create("https://mail.qq.com"))
                .build();
        HttpResponse<byte[]> response = httpClient.send(request, HttpResponse.BodyHandlers.ofByteArray());
        String body = new String(UnZipper.unGzipByBytes(response.body()));
        //设置referer uri
        refererUri = request.uri().toString();
        //返回解析出的XLogin url
        return RegexSearcher.matchFirst(body, "(?<=src=\")https://xui.ptlogin2.qq.com((?!\").)*(?=\")");
    }

    /**
     * 获取cookie
     *
     * @param xLoginUrl 目的uri
     * @throws IOException          io
     * @throws InterruptedException in
     */
    private void xLogin(String xLoginUrl) throws IOException, InterruptedException {
        HttpRequest request = baseRequestBuilder
                .copy()
                .uri(URI.create(xLoginUrl))
                .header("Referer", refererUri)
                .build();
        //重置referer uri
        refererUri = request.uri().toString();
        //发送请求
        httpClient.send(request, HttpResponse.BodyHandlers.ofByteArray());
    }

    private String checkSig(String checkSigUrl) throws IOException, InterruptedException {

        HttpRequest request = baseRequestBuilder
                .copy()
                .uri(URI.create(checkSigUrl))
                .setHeader("Referer", refererUri)
                .build();
        HttpResponse<String> response = httpClient.send(request, HttpResponse.BodyHandlers.ofString());
        return HttpResponseUtil.getLocation(response);
    }

    /**
     * 获取loginVt url
     *
     * @param readUrl url
     * @return loginVt url
     * @throws IOException          异常
     * @throws InterruptedException 异常
     */
    private String readTemplate(String readUrl) throws IOException, InterruptedException {

        HttpRequest request = baseRequestBuilder
                .copy()
                .uri(URI.create(readUrl))
                .setHeader("Referer", refererUri)
                .build();
        HttpResponse<byte[]> response = httpClient.send(request, HttpResponse.BodyHandlers.ofByteArray());
        String body = new String(UnZipper.unGzipByBytes(response.body()));
        return RegexSearcher.matchFirst(body, "(?<=top.location.href((?!\")[\\s\\S]){0,100}\")((?!\").*)(?=\")");
    }

    private String loginVt(String loginVtUrl) throws IOException, InterruptedException {

        HttpRequest request = baseRequestBuilder
                .copy()
                .uri(URI.create(loginVtUrl))
                .setHeader("Referer", "https://mail.qq.com/")
                .build();
        HttpResponse<byte[]> response = httpClient.send(request, HttpResponse.BodyHandlers.ofByteArray());
        //解析并设置sidu
        String location = HttpResponseUtil.getLocation(response);
        String sidu = RegexSearcher.matchFirst(location, "(?<=sid=)((?!&).)*(?=&)");
        //如果sidu不为空,提取
        if (!sidu.isEmpty()) {
            cookieManager.getCookieStore().add(URI.create("mail.qq.com"), new HttpCookie("sidu", sidu));
        }
        return location;
    }

    /**
     * 输入独立密码
     *
     * @param independentUrl 独立密码url
     * @throws IOException          io
     * @throws InterruptedException in
     */
    private boolean independentPage(String independentUrl) throws IOException, InterruptedException {
        //从独立密码url中提取参数
        String clientUin = RegexSearcher.matchFirst(independentUrl, "(?<=clientuin=)((?!&).)*(?=&)");
        String errType = RegexSearcher.matchFirst(independentUrl, "(?<=errtype=)((?!&).)*(?=&)");
        String tfCont = RegexSearcher.matchFirst(independentUrl, "(?<=tfcont=)((?!&).)*(?=&)");
        String spCache = RegexSearcher.matchFirst(independentUrl, "(?<=sp=)((?!&).)*(?=&)");
        //输入独立密码

        String url = "https://mail.qq.com/cgi-bin/login?sid=,2,zh_CN";
        //添加新的cookie
        cookieManager.getCookieStore().add(URI.create("mail.qq.com"), new HttpCookie("spcache", spCache));
        independentPwd = JOptionPane.showInputDialog("请输入独立密码");
        printlnInfo("独立密码:" + independentPwd);
        //post data
        String postDataSb = "org_errtype=" + errType +
                "&tfcont=" + tfCont +
                "&delegate_url=" +
                "&f=html" +
                "&starttime=" +
                "&chg=0" +
                "&ept=0" +
                "&ppp=" +
                "&ts=" + (System.currentTimeMillis() / 1000) +
                "&vt=secondpwd" +
                "&spcache=" + spCache +
                "&clientaddr=" + clientUin +
                "&ignore_me=ignore_me" +
                "&pp=" + independentPwd +
                "&p=" + independentPwd +
                "&btlogin=%B5%C7%C2%BC";
        HttpRequest request = baseRequestBuilder
                .copy()
                .uri(URI.create(url))
                .setHeader("Referer", "https://mail.qq.com/")
                .setHeader("Cookie", CookieParser.getAllCookie(cookieManager))
                .POST(HttpRequest.BodyPublishers.ofString(postDataSb))
                .build();
        HttpResponse<byte[]> response = httpClient.send(request, HttpResponse.BodyHandlers.ofByteArray());
        String locationUrl = HttpResponseUtil.getLocation(response);
        String sidu = RegexSearcher.matchFirst(locationUrl, "(?<=sid=)((?!&).)*(?=&)");
        //如果不包含sid,验证独立密码失败,提示错误原因,否则登录成功,添加sid到CookieManager
        if (sidu.isEmpty()) {
            String charset = HttpResponseUtil.getCharset(response);
            String body = new String(UnZipper.unGzipByBytes(response.body()), charset);
            String backMsg = RegexSearcher.matchFirst(body, "(?<=login_box_msg=\">)((?!</div>).)*(?=</div>)");
            printlnInfo(backMsg);
            return false;
        } else {
            cookieManager.getCookieStore().add(URI.create("mail.qq.com"), new HttpCookie("sidu", sidu));
            return true;
        }
    }

    /**
     * 开通邮箱
     *
     * @param openMailUri 开通邮箱uri
     * @throws IOException          io
     * @throws InterruptedException in
     */
    private boolean openEMail(String openMailUri) throws IOException, InterruptedException {
        HttpRequest request = baseRequestBuilder
                .copy()
                .uri(URI.create(openMailUri))
                .build();
        httpClient.send(request, HttpResponse.BodyHandlers.ofByteArray());
        String sid = RegexSearcher.matchFirst(openMailUri, "(?<=sid=)((?!&).)*(?=&)");
        String activeMailUri = "https://mail.qq.com/cgi-bin/autoactivation?sid=" + sid + "&actmode=7&colid=102&t=activesuccess&sid=" + sid;
        request = baseRequestBuilder
                .copy()
                .uri(URI.create(activeMailUri))
                .method("POST", HttpRequest.BodyPublishers.ofString(""))
                .build();
        HttpResponse<byte[]> response = httpClient.send(request, HttpResponse.BodyHandlers.ofByteArray());
        String body = HttpResponseUtil.getBody(response).toLowerCase();
        return body.contains("success");
    }

    /**
     * 二维码扫描器
     */
    private class QrScanner {

        /**
         * 获取二维码
         *
         * @throws IOException          io
         * @throws InterruptedException in
         */
        private void getQrImage() throws IOException, InterruptedException {

            HttpRequest request = baseRequestBuilder
                    .copy()
                    .uri(URI.create("https://ssl.ptlogin2.qq.com/ptqrshow?appid=522005705&e=2&l=M&s=3&d=72&v=4&t=0.4982519163707848&daid=4&pt_3rd_aid=0"))
                    .setHeader("Accept", "image/webp,image/apng,image/*,*/*;q=0.8")
                    .setHeader("Referer", refererUri)
                    .build();
            //二维码存储路径
            Path qrCodePath = Path.of("F:\\workspace\\java\\MailCrawler\\src\\main\\resources\\cache\\OrCode" + System.currentTimeMillis() + ".png");
            //发送请求,存储验证码到本地
            httpClient.send(request, HttpResponse.BodyHandlers.ofFile(qrCodePath));
            printlnInfo("二维码已存储至:" + qrCodePath);
        }

        /**
         * 获取二维码状态
         *
         * @return 二维码当前状态
         * @throws IOException          IO
         * @throws InterruptedException in
         */
        private String getQrStatus() throws IOException, InterruptedException {

            String qrsig = CookieParser.getSpecifiedCookie(cookieManager, "qrsig");
            int qrToken = CharTransfer.hash(qrsig);
            String url = "https://ssl.ptlogin2.qq.com/ptqrlogin?" +
                    "u1=https%3A%2F%2Fmail.qq.com%2Fcgi-bin%2Freadtemplate%3Fcheck%3Dfalse%26t%3Dloginpage_new_jump%26vt%3Dpassport%26vm%3Dwpt%26ft%3Dloginpage%26target%3D" +
                    "&ptqrtoken=" + qrToken +
                    "&ptredirect=0" +
                    "&h=1" +
                    "&t=" +
                    "&g=1" +
                    "&from_ui=1" +
                    "&ptlang=2052" +
                    "&action=6-0-" + System.currentTimeMillis() +
                    "&js_ver=10291" +
                    "&js_type=1" +
                    "&login_sig=" + CookieParser.getSpecifiedCookie(cookieManager, "pt_login_sig") +
                    "&pt_uistyle=25" +
                    "&aid=522005705" +
                    "&daid=4" +
                    "&";
            HttpRequest request = baseRequestBuilder
                    .copy()
                    .uri(URI.create(url))
                    .setHeader("Accept", "*/*")
                    .setHeader("Referer", refererUri)
                    .setHeader("Cookie", "qrsig=" + qrsig)
                    .build();
            HttpResponse<byte[]> response = httpClient.send(request, HttpResponse.BodyHandlers.ofByteArray());

            return new String(UnZipper.unGzipByBytes(response.body()));
        }

        /**
         * 监听二维码状态
         *
         * @return 跳转url
         * @throws IOException          IO
         * @throws InterruptedException In
         */
        private String watchingQr() throws IOException, InterruptedException {
            //首先获取一张二维码
            getQrImage();
            //跳转url
            String checkSigUrl = null;
            //重复次数
            int repeatTimes = 1000;
            //间隔时间
            int repeatInterval = 1;
            while (repeatTimes-- > 0) {
                String statusBody = getQrStatus();
                String statusCode = RegexSearcher.matchFirst(statusBody, "(?<=ptuiCB\\(')((?!').)*(?=')");
                printlnInfo(statusBody);
                if (statusCode.equals(QQ_LOGIN_STATUS_OK)) {
                    //成功已扫码
                    checkSigUrl = RegexSearcher.matchFirst(statusBody, "(?<=ptuiCB\\('0','0',')((?!').)*(?=')");
                    break;
                } else if (statusCode.equals(StatusCode.QQ_LOGIN_QR_INVALID)) {
                    printlnWarning("二维码已失效,即将重新获取");
                    //二维码失效,重新请求验证码
                    getQrImage();
                }
                try {
                    //休眠间隔
                    Thread.sleep(1000 * repeatInterval);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            if (Objects.isNull(checkSigUrl)) {
                throw new NullPointerException("未能获取到跳转url");
            }

            return checkSigUrl;
        }
    }

    /**
     * 账号密码登录器
     */
    private class PasswordInputer {

        private String ptVerifySessionV1;
        private String verifyCode;
        private String encryptCode;
        private String randSalt;
        private String sliderImageBaseUrl;
        private String sess;
        private String sid;
        private String websig;
        private String vsig;
        private String ptVCode;

        /**
         * 获取账号对应的各个加密参数
         *
         * @throws IOException          IO
         * @throws InterruptedException In
         */
        private boolean checkRegMaster() throws IOException, InterruptedException {

            String url = "https://ssl.ptlogin2.qq.com/check?" + "regmaster=" +
                    "&pt_tea=2" +
                    "&pt_vcode=1" +
                    "&uin=" + account +
                    "&appid=522005705" +
                    "&js_ver=10291" +
                    "&js_type=1" +
                    "&login_sig=" + CookieParser.getSpecifiedCookie(cookieManager, "pt_login_sig") +
                    "&u1=https%3A%2F%2Fmail.qq.com%2Fcgi-bin%2Freadtemplate%3Fcheck%3Dfalse%26t%3Dloginpage_new_jump%26vt%3Dpassport%26vm%3Dwpt%26ft%3Dloginpage%26target%3D" +
                    "&r=0.8591487157144195" +
                    "&pt_uistyle=25";
            HttpRequest request = baseRequestBuilder
                    .copy()
                    .uri(URI.create(url))
                    .setHeader("Cookie", CookieParser.getAllCookie(cookieManager))
                    .setHeader("Referer", refererUri)
                    .build();
            HttpResponse<byte[]> response = httpClient.send(request, HttpResponse.BodyHandlers.ofByteArray());
            String charset = HttpResponseUtil.getCharset(response);
            String body = new String(response.body(), charset);
            LinkedList<String> codeList = RegexSearcher.match(body, "(?<=')((?!')(?!,).)*(?=')");
            //返回是否需要滑块验证码
            ptVCode = codeList.get(0);
            //验证码1
            verifyCode = codeList.get(1);
            //加密秘钥
            encryptCode = codeList.get(2).replace("\\x", "");
            //验证码2
            ptVerifySessionV1 = codeList.get(3);
            //模式
            randSalt = codeList.get(4);

            if (verifyCode.isEmpty()) {
                throw new IOException("没有获取到验证码");
            }

            return ptVCode.equals(StatusCode.QQ_NEED_SLIDER_FLAG);
        }

        /**
         * 获取滑块验证码
         *
         * @throws IOException          io
         * @throws InterruptedException in
         */
        private String initSliderImage() throws IOException, InterruptedException {
            //初始化获取滑块验证码参数
            String url = "https://ssl.captcha.qq.com/cap_union_prehandle?aid=522005705" +
                    "&captype=" +
                    "&curenv=inner" +
                    "&protocol=https" +
                    "&clientype=2" +
                    "&disturblevel=" +
                    "&apptype=2" +
                    "&noheader=" +
                    "&color=" +
                    "&showtype=embed" +
                    "&fb=1" +
                    "&theme=" +
                    "&lang=2052" +
                    //ua固定值
                    "&ua=TW96aWxsYS81LjAgKFdpbmRvd3MgTlQgMTAuMDsgV2luNjQ7IHg2NCkgQXBwbGVXZWJLaXQvNTM3LjM2IChLSFRNTCwgbGlrZSBHZWNrbykgQ2hyb21lLzczLjAuMzY2NC4zIFNhZmFyaS81MzcuMzY=" +
                    "&grayscale=1" +
                    "&cap_cd=" + verifyCode +
                    "&uid=2521957520" +
                    "&wxLang=" +
                    "&subsid=1" +
                    "&callback=_aq_745219" +
                    "&sess=";
            HttpRequest request = baseRequestBuilder
                    .copy()
                    .uri(URI.create(url))
                    .setHeader("Referer", refererUri)
                    .setHeader("Cookie", "ptisp=ctc")
                    .build();
            HttpResponse<byte[]> response = httpClient.send(request, HttpResponse.BodyHandlers.ofByteArray());
            String charset = HttpResponseUtil.getCharset(response);
            String body = new String(UnZipper.unGzipByBytes(response.body()), charset);
            LinkedList<String> tempList = RegexSearcher.match(body, "(?<=\":\")((?!\")(?!,).)*(?=\")");
            sess = tempList.get(7);
            sid = tempList.get(9);

            //继续初始化
            url = "https://ssl.captcha.qq.com/cap_union_new_show?aid=522005705" +
                    "&captype=" +
                    "&curenv=inner" +
                    "&protocol=https" +
                    "&clientype=2" +
                    "&disturblevel=" +
                    "&apptype=2" +
                    "&noheader=" +
                    "&color=" +
                    "&showtype=embed" +
                    "&fb=1" +
                    "&theme=" +
                    "&lang=2052" +
                    "&ua=TW96aWxsYS81LjAgKFdpbmRvd3MgTlQgMTAuMDsgV2luNjQ7IHg2NCkgQXBwbGVXZWJLaXQvNTM3LjM2IChLSFRNTCwgbGlrZSBHZWNrbykgQ2hyb21lLzczLjAuMzY2NC4zIFNhZmFyaS81MzcuMzY=" +
                    "&grayscale=1" +
                    "&subsid=2" +
                    "&sess=" + sess +
                    "&fwidth=0" +
                    "&sid=" + sid +
                    "&forcestyle=undefined" +
                    "&wxLang=" +
                    "&tcScale=0" +
                    "&noBorder=noborder" +
                    "&uid=" + account +
                    "&cap_cd=" + verifyCode +
                    "&rnd=138744" +
                    "&TCapIframeLoadTime=248" +
                    "&prehandleLoadTime=63" +
                    "&createIframeStart=" + System.currentTimeMillis();

            request = baseRequestBuilder
                    .copy()
                    .uri(URI.create(url))
                    .setHeader("Referer", refererUri)
                    .setHeader("Cookie", "ptsip=ctc")
                    .build();
            response = httpClient.send(request, HttpResponse.BodyHandlers.ofByteArray());
            charset = HttpResponseUtil.getCharset(response);
            body = new String(UnZipper.unGzipByBytes(response.body()), charset);
            sliderImageBaseUrl = RegexSearcher.matchFirst(body, "(?<=cdnPic1:\")((?!\").)*(?=\")");
            websig = RegexSearcher.matchFirst(body, "(?<=websig:\")((?!\").)*(?=\")");
            vsig = RegexSearcher.matchFirst(body, "(?<=vsig:\")((?!\").)*(?=\")");
            refererUri = request.uri().toString();
            String captchaUrl = RegexSearcher.matchFirst(body, "(?<=src=\")https:((?!\").)*tcaptcha-slide((?!\").)*(?=\")");

            long millis = System.currentTimeMillis();
            //下载滑块验证码加密函数
            String taptcha = downloadJs(captchaUrl, millis);

            //替换目的captcha.js为本地js
            body = body.replaceAll("(?<=src=\")https:((?!\").)*tcaptcha-slide((?!\").)*(?=\")", taptcha)
                    //删除匿名
                    .replace("crossorigin=\"anonymous\"", "")
                    .replace("id=\"tcOperation\"", "id=\"tcOperation\" style=\"width: 300px;height: 225.16px;\"");
            String htmlFilePath = "F:\\workspace\\java\\MailCrawler\\src\\main\\resources\\cache\\cap_union_new" + millis + ".html";
            //下载滑块的html
            FileManager.overwrite(htmlFilePath, body.getBytes(StandardCharsets.UTF_8));

            return htmlFilePath;
        }

        /**
         * 下载tcaptcha.js和tdc.js
         *
         * @param captchaUrl url
         * @throws IOException          io
         * @throws InterruptedException in
         */
        private String downloadJs(String captchaUrl, long millis) throws IOException, InterruptedException {

            HttpRequest request = baseRequestBuilder
                    .copy()
                    .uri(URI.create(captchaUrl))
                    .build();
            HttpResponse<byte[]> response = httpClient.send(request, HttpResponse.BodyHandlers.ofByteArray());
            String body = HttpResponseUtil.getBody(response);
            body = body.replaceAll("var v((?![)]}}[)]).)*[)]}}[)]",
                    "document.write(\"<p id='vertexCode'>\"+l.ans+\"</p>\");" +
                            "document.write(\"<p id='vsigCode'>\"+l.vsig+\"</p>\");" +
                            "document.write(\"<p id='cdataCode'>\"+l.cdata+\"</p>\");" +
                            "document.write(\"<p id='websigCode'>\"+l.websig+\"</p>\");" +
                            "document.write(\"<p id='subcapclassCode'>\"+l.subcapclass+\"</p>\");" +
                            "document.write(\"<p id='encryptedTrackCode'>\"+y.collectdata+\"=\"+l[y.collectdata]+\"</p>\");" +
                            "document.write(\"<p id='fpinfoCode'>\"+l.fpinfo+\"</p>\");" +
                            "document.write(\"<p id='eksCode'>\"+l.eks+\"</p>\");" +
                            "document.write(\"<p id='tlgCode'>\"+l.tlg+\"</p>\");")
                    .replace("//captcha.gtimg.com", "https:////captcha.gtimg.com");
            String captcha = "captcha_slide" + millis + ".js";
            FileManager.overwrite("F:\\workspace\\java\\MailCrawler\\src\\main\\resources\\cache\\" + captcha, body.getBytes(StandardCharsets.UTF_8));

            return captcha;
        }

        /**
         * 下载滑块验证码背景图片
         *
         * @throws IOException          io
         * @throws InterruptedException in
         */
        private byte[] downloadBackgroundSliderImage() throws IOException, InterruptedException {

            String url = sliderImageBaseUrl +
                    "?aid=522005705" +
                    "&captype=" +
                    "&curenv=inner" +
                    "&protocol=https" +
                    "&clientype=2" +
                    "&disturblevel=" +
                    "&apptype=2" +
                    "&noheader=" +
                    "&color=" +
                    "&showtype=embed" +
                    "&fb=1" +
                    "&theme=" +
                    "&lang=2052" +
                    "&ua=TW96aWxsYS81LjAgKFdpbmRvd3MgTlQgMTAuMDsgV2luNjQ7IHg2NCkgQXBwbGVXZWJLaXQvNTM3LjM2IChLSFRNTCwgbGlrZSBHZWNrbykgQ2hyb21lLzczLjAuMzY2NC4zIFNhZmFyaS81MzcuMzY=" +
                    "&grayscale=1" +
                    "&subsid=" + (1 + 2) +
                    "&sess=" + sess +
                    "&fwidth=0" +
                    "&sid=" + sid +
                    "&forcestyle=undefined" +
                    "&wxLang=" +
                    "&tcScale=0" +
                    "&noBorder=noborder" +
                    "&uid=" + account +
                    "&cap_cd=" + verifyCode +
                    "&rnd=138744" +
                    "&TCapIframeLoadTime=248" +
                    "&prehandleLoadTime=63" +
                    "&createIframeStart=" + createIframeStart +
                    "&rand=53722395" +
                    "&websig=" + websig +
                    "&vsig=" + vsig +
                    "&img_index=" + 1;

            HttpRequest request = baseRequestBuilder
                    .copy()
                    .uri(URI.create(url))
                    .setHeader("Referer", refererUri)
                    .setHeader("Accept", "image/webp,image/apng,image/*,*/*;q=0.8")
                    .setHeader("Cookie", "ptsip=ctc")
                    .build();
            Path path = Path.of("F:\\workspace\\java\\MailCrawler\\src\\main\\resources\\cache\\hycdn_1_" + System.currentTimeMillis() + ".jpg");
            httpClient.send(request, HttpResponse.BodyHandlers.ofFile(path));
            //HttpResponse<byte[]> response = httpClient.send(request, HttpResponse.BodyHandlers.ofByteArray());

            //return UnZipper.unGzipByBytes(response.body());
            System.exit(0);
            return null;
        }

        /**
         * 校验滑块验证码
         *
         * @throws IOException          io
         * @throws InterruptedException io
         */
        private void checkoutSlider(String htmlFilePath) throws IOException, InterruptedException, VerifyException {

            //在此处获取滑块背景图片字节数组
            byte[] imageBytes = downloadBackgroundSliderImage();
            //获取滑块参数
            Map<String, String> slideParameters = TecentSlider.verify(htmlFilePath, imageBytes);
            if (Objects.isNull(slideParameters)) {
                throw new VerifyException("验证码参数Map为null");
            }

            //发送请求,获取fp3_id1
            HttpRequest request = baseRequestBuilder
                    .copy()
                    .uri(URI.create("https://dj.captcha.qq.com/dfpReg?0=Mozilla%2F5.0%20(Windows%20NT%2010.0%3B%20Win64%3B%20x64)%20AppleWebKit%2F537.36%20(KHTML%2C%20like%20Gecko)%20Chrome%2F73.0.3664.3%20Safari%2F537.36&1=zh-CN&2=1.8&3=1.8&4=24&5=4&6=-480&7=1&8=1&9=1&10=u&11=function&12=u&13=Win32&14=1&15=9dcc2da81f0e59e03185ad3db82acb72&16=e63d5bbf5ce8ea400eb02a990fbe8f4b&17=a1f937b6ee969f22e6122bdb5cb48bde&18=0&19=765d481f3979f78846e8a2519e74e155&20=10401920241080192024&21=1%3B&22=1%3B1%3B1%3B1%3B1%3B1%3B1%3B0%3B1%3Bobject0UTF-8&23=1&24=0%3B0&25=91904106b11f66e1774fe8c891acdba7&26=48000_2_1_0_2_explicit_speakers&27=d7959e801195e05311be04517d04a522&28=ANGLE(Intel(R)HDGraphics630Direct3D11vs_5_0ps_5_0)&29=3c63bd1eb09bebc1da3388960627ea84&30=cee2ad1773391cdf6e49e9964bc7303c&31=0&32=0&33=0&34=0&35=0&36=0&37=0&38=0&39=0&40=0&41=0&42=0&43=0&44=0&45=0&46=0&47=0&48=0&49=0&50=0&fesig=7432602075767661964&ut=1037&appid=0&refer=https%3A%2F%2Fssl.captcha.qq.com%2Fcap_union_new_show&domain=ssl.captcha.qq.com&fph=&fpv=0.0.15&ptcz=&callback=_fp_037630"))
                    .setHeader("Accept", "*/*")
                    .build();
            HttpResponse<byte[]> response = httpClient.send(request, HttpResponse.BodyHandlers.ofByteArray());
            HttpHeaders httpHeaders = response.headers();
            Optional<String> cookieOp = httpHeaders.firstValue("Set-Cookie");
            String fp3Id1;
            if (cookieOp.isPresent()) {
                fp3Id1 = RegexSearcher.matchFirst(cookieOp.get(), "(?<=fp3_id1=)((?!;)(?!,).)*(?=[;,]{1})");
            } else {
                String charset = HttpResponseUtil.getCharset(response);
                String body = new String(UnZipper.unGzipByBytes(response.body()), charset);
                fp3Id1 = RegexSearcher.matchFirst(body, "(?<=fpsig\":\")((?!\").)*(?=\")");
            }

            //验证滑块
            String cookie = "ptui_loginuin=" + account +
                    "; TDC_itoken=1580537537%3A" + (System.currentTimeMillis() / 1000) +
                    "; fp3_id1=" + fp3Id1 +
                    "; fp3_id1=" + fp3Id1;
            //postData
            String postData = "aid=522005705" +
                    "&captype=" +
                    "&curenv=inner" +
                    "&protocol=https" +
                    "&clientype=2" +
                    "&disturblevel=" +
                    "&apptype=2" +
                    "&noheader=" +
                    "&color=" +
                    "&showtype=embed" +
                    "&fb=1" +
                    "&theme=" +
                    "&lang=2052" +
                    "&ua=TW96aWxsYS81LjAgKFdpbmRvd3MgTlQgMTAuMDsgV2luNjQ7IHg2NCkgQXBwbGVXZWJLaXQvNTM3LjM2IChLSFRNTCwgbGlrZSBHZWNrbykgQ2hyb21lLzczLjAuMzY2NC4zIFNhZmFyaS81MzcuMzY=" +
                    "&grayscale=1" +
                    "&subsid=2" +
                    "&sess=" + sess +
                    "&fwidth=0" +
                    "&sid=" + sid +
                    "&forcestyle=undefined" +
                    "&wxLang=" +
                    "&tcScale=0" +
                    "&noBorder=noborder" +
                    "&uid=" + account +
                    "&cap_cd=" + verifyCode +
                    "&rnd=102323" +
                    "&TCapIframeLoadTime=231" +
                    "&prehandleLoadTime=25" +
                    "&createIframeStart=" + createIframeStart +
                    "&ans=" + slideParameters.get("vertexCode") +
                    "&vsig=" + slideParameters.get("vsigCode") +
                    "&cdata=" + slideParameters.get("cdataCode") +
                    "&websig=" + slideParameters.get("websigCode") +
                    "&subcapclass=" + slideParameters.get("subcapclassCode") +
                    "&" + slideParameters.get("encryptedTrackCode") +
                    "&fpinfo=" + slideParameters.get("fpinfoCode") +
                    "&eks=" + slideParameters.get("eksCode") +
                    "&tlg=" + slideParameters.get("tlgCode") +
                    "&vlg=0_0_0";
            request = baseRequestBuilder
                    .copy()
                    .uri(URI.create("https://ssl.captcha.qq.com/cap_union_new_verify"))
                    .setHeader("Accept", "application/json, text/javascript, */*; q=0.01")
                    .setHeader("Cookie", cookie)
                    .POST(HttpRequest.BodyPublishers.ofString(postData))
                    .build();
            response = httpClient.send(request, HttpResponse.BodyHandlers.ofByteArray());
            String charset = HttpResponseUtil.getCharset(response);
            String body = new String(UnZipper.unGzipByBytes(response.body()), charset).replace(" ", "");
            String errorCode = RegexSearcher.matchFirst(body, "(?<=errorCode\":\")((?!\").)*(?=\")");
            if (!"0".equals(errorCode)) {
                String errMessage = RegexSearcher.matchFirst(body, "(?<=errMessage\":\")((?!\").)*(?=\")");
                throw new VerifyException(errMessage);
            }
            //解析并重置验证码
            verifyCode = RegexSearcher.matchFirst(body, "(?<=randstr\":\")((?!\").)*(?=\")");
            //重置cookie中的ptvfsession
            ptVerifySessionV1 = RegexSearcher.matchFirst(body, "(?<=ticket\":\")((?!\").)*(?=\")");
        }

        private void checkoutSlider() {
            String sliderResult = null;

            while (Objects.isNull(sliderResult)) {
                try {
                    sliderResult = ThirdPart.qqMailSlide(account, verifyCode, proxyEntity);
                } catch (SocketException e) {
                    //出现网络问题时,更换代理
                    ProxyManager.returnProxy(proxyEntity, 1);
                    proxyEntity = ProxyManager.getProxy();
                } catch (IOException | InterruptedException e) {
                    e.printStackTrace();
                }
            }
            //解析并重置验证码
            verifyCode = RegexSearcher.matchFirst(sliderResult, "(?<=randstr\":\")((?!\").)*(?=\")");
            //重置cookie中的ptvfsession
            ptVerifySessionV1 = RegexSearcher.matchFirst(sliderResult, "(?<=ticket\":\")((?!\").)*(?=\")");
        }

        /**
         * 提交账号密码
         *
         * @return checkSIG
         * @throws IOException          io
         * @throws InterruptedException in
         */
        private String loginU() throws IOException, InterruptedException {

            String encryptPwd = encrypt(encryptCode, verifyCode);
            String url = "https://ssl.ptlogin2.qq.com/login?u=" + account +
                    "&verifycode=" + verifyCode +
                    "&pt_vcode_v1=" + ptVCode +
                    "&pt_verifysession_v1=" + ptVerifySessionV1 +
                    "&p=" + encryptPwd +
                    "&pt_randsalt=" + randSalt +
                    "&u1=https%3A%2F%2Fmail.qq.com%2Fcgi-bin%2Freadtemplate%3Fcheck%3Dfalse%26t%3Dloginpage_new_jump%26vt%3Dpassport%26vm%3Dwpt%26ft%3Dloginpage%26target%3D%26account%3D" + account +
                    "&ptredirect=0" +
                    "&h=1" +
                    "&t=1" +
                    "&g=1" +
                    "&from_ui=1" +
                    "&ptlang=2052" +
                    "&action=1-22-" + System.currentTimeMillis() +
                    "&js_ver=10291" +
                    "&js_type=1" +
                    "&login_sig=" + CookieParser.getSpecifiedCookie(cookieManager, "pt_login_sig") +
                    "&pt_uistyle=25" +
                    "&aid=522005705" +
                    "&daid=4" +
                    "&";
            HttpRequest request = baseRequestBuilder
                    .copy()
                    .uri(URI.create(url))
                    .setHeader("Cookie", "ptui_loginuin=" + account)
                    .build();
            HttpResponse<byte[]> response = httpClient.send(request, HttpResponse.BodyHandlers.ofByteArray());
            String charset = HttpResponseUtil.getCharset(response);
            String body = new String(response.body(), charset);
            String loginFlag = RegexSearcher.matchFirst(body, "(?<=ptuiCB\\(')((?!').){1}(?=')");
            String checkSigUrl;
            if (QQ_LOGIN_STATUS_OK.equals(loginFlag)) {
                checkSigUrl = RegexSearcher.matchFirst(body, "(?<=')https((?!').)*(?=','0)");
                printlnInfo("登录成功:" + body);
            } else {
                String errMsg = RegexSearcher.matchFirst(body, "(?<=')((?!').){5,50}(?=')");
                printlnInfo("登录失败:" + body);
                throw new IOException("登录失败:" + errMsg);
            }

            return checkSigUrl;
        }

        /**
         * 使用js引擎加密密码
         *
         * @param encryptCode 加密代码
         * @param verifyCode  验证码
         * @return 加密后的密码
         */
        private String encrypt(String encryptCode, String verifyCode) {
            String method = "encode('" + password + "','" + encryptCode + "','" + verifyCode + "')";

            return jsInvoker.invokeJs(method);
        }
    }
}
